import type { ComponentResolver } from '../src'
import { readFile, writeFile } from 'node:fs/promises'
import path from 'node:path'
import { describe, expect, it } from 'vitest'
import { Context } from '../src/core/context'
import { getDeclaration, parseDeclaration } from '../src/core/declaration'

const root = path.resolve(__dirname, '../examples/vite-vue3')
const resolver: ComponentResolver[] = [
  {
    type: 'component',
    resolve: name => ({ from: `test/component/${name}` }),
  },
  {
    type: 'directive',
    resolve: name => ({ from: `test/directive/${name}` }),
  },
]

describe('dts', () => {
  it('getDeclaration', async () => {
    const ctx = new Context({
      resolvers: resolver,
      directives: true,
    })
    const code = `
const _component_test_comp = _resolveComponent("test-comp")
const _directive_loading = _resolveDirective("loading")`
    await ctx.transform(code, '')

    const declarations = getDeclaration(ctx, 'test.d.ts')
    expect(declarations).toMatchSnapshot()
  })

  it('tsx', async () => {
    const ctx = new Context({
      resolvers: resolver,
      directives: true,
      dts: true,
      dtsTsx: true,
    })
    const code = `
const _component_test_comp = _resolveComponent("test-comp")
const _directive_loading = _resolveDirective("loading")`
    await ctx.transform(code, '')

    const declarations = getDeclaration(ctx, 'test.d.ts')
    expect(declarations).toMatchSnapshot()
  })

  it('writeDeclaration', async () => {
    const filepath = path.resolve(__dirname, 'tmp/dts-test.d.ts')
    const ctx = new Context({
      resolvers: resolver,
      directives: true,
      dts: filepath,
    })
    const code = `
const _component_test_comp = _resolveComponent("test-comp")
const _directive_loading = _resolveDirective("loading")`
    await ctx.transform(code, '')
    await ctx._generateDeclaration()

    expect(await readFile(filepath, 'utf-8')).matchSnapshot()
  })

  it.each(['append', 'overwrite'] as const)('writeDeclaration - %s', async (syncMode) => {
    const filepath = path.resolve(__dirname, 'tmp/dts-test-sync-mode.d.ts')
    await writeFile(
      filepath,
      `
declare module 'vue' {
  export interface GlobalComponents {
    SomeComp: typeof import('test/component/SomeComp')['default']
    TestComp: typeof import('test/component/OldComp')['default']
  }
  export   interface   GlobalDirectives{
    // with comment: b
    // a:
    vSome: typeof import('test/directive/Some')['default'];vDirective:typeof import('foo')
  }
}`,
      'utf-8',
    )
    const ctx = new Context({
      resolvers: resolver,
      directives: true,
      dts: filepath,
      syncMode,
    })
    const code = `
const _component_test_comp = _resolveComponent("test-comp")
const _directive_loading = _resolveDirective("loading")`
    await ctx.transform(code, '')
    await ctx._generateDeclaration()

    const contents = await readFile(filepath, 'utf-8')
    expect(contents).matchSnapshot()
  })

  it('components only', async () => {
    const ctx = new Context({
      resolvers: resolver,
      directives: true,
    })
    const code = 'const _component_test_comp = _resolveComponent("test-comp")'
    await ctx.transform(code, '')

    const declarations = getDeclaration(ctx, 'test.d.ts')
    expect(declarations).toMatchSnapshot()
  })

  it('vue 2.7 components only', async () => {
    const ctx = new Context({
      resolvers: resolver,
      directives: true,
      version: 2.7,
    })
    const code = 'const _component_test_comp = _c("test-comp")'
    await ctx.transform(code, '')

    const declarations = getDeclaration(ctx, 'test.d.ts')
    expect(declarations).toMatchSnapshot()
  })

  it('directive only', async () => {
    const ctx = new Context({
      resolvers: resolver,
      directives: true,
      types: [],
    })
    const code = 'const _directive_loading = _resolveDirective("loading")'
    await ctx.transform(code, '')

    const declarations = getDeclaration(ctx, 'test.d.ts')
    expect(declarations).toMatchSnapshot()
  })

  it('parseDeclaration', async () => {
    const code = `
/* eslint-disable */
// generated by unplugin-vue-components
// We suggest you to commit this file into source control
// Read more: https://github.com/vuejs/core/pull/3399
export {}

/* prettier-ignore */
declare module 'vue' {
  export interface GlobalComponents {
    ComponentA: typeof import('./src/components/ComponentA.vue')['default']
    ComponentB: typeof import('./src/components/ComponentB.vue')['default']
    ComponentC: typeof import('./src/components/component-c.vue')['default']
  }
}`

    const imports = parseDeclaration(code)
    expect(imports).matchSnapshot()
  })

  it('parseDeclaration - has icon component like <IMdi:diceD12>', async () => {
    const code = `
/* eslint-disable */
// generated by unplugin-vue-components
// We suggest you to commit this file into source control
// Read more: https://github.com/vuejs/core/pull/3399
export {}

/* prettier-ignore */
declare module 'vue' {
  export interface GlobalComponents {
    ComponentA: typeof import('./src/components/ComponentA.vue')['default']
    'IMdi:diceD12': typeof import('~icons/mdi/dice-d12')['default']
    IMdiLightAlarm: typeof import('~icons/mdi-light/alarm')['default']
  }
}`

    const imports = parseDeclaration(code)
    expect(imports).matchSnapshot()
  })

  it('parseDeclaration - with directives', async () => {
    const code = `
/* eslint-disable */
// generated by unplugin-vue-components
// We suggest you to commit this file into source control
// Read more: https://github.com/vuejs/core/pull/3399
export {}

/* prettier-ignore */
declare module 'vue' {
  export interface GlobalComponents {
    ComponentA: typeof import('./src/components/ComponentA.vue')['default']
    'IMdi:diceD12': typeof import('~icons/mdi/dice-d12')['default']
    IMdiLightAlarm: typeof import('~icons/mdi-light/alarm')['default']
  }

  export interface GlobalDirectives {
    vDirective: typeof import('foo')
    vLoading: typeof import('test/directive/Loading')['default']
    vSome: typeof import('test/directive/Some')['default']
  }
}`

    const imports = parseDeclaration(code)
    expect(imports).matchSnapshot()
  })

  it('generate components with prefix', async () => {
    const ctx = new Context({
      resolvers: resolver,
      directives: true,
      prefix: 'CustomPrefix',
      dirs: ['src/components'],
    })
    ctx.setRoot(root)
    const code = `
const _component_test_comp = _resolveComponent("test-comp")
const _directive_loading = _resolveDirective("loading")`
    await ctx.transform(code, '')

    const declarations = getDeclaration(ctx, 'test.d.ts')
    expect(declarations).toMatchSnapshot()
  })
})
